﻿using System;
using System.Security.Cryptography;
using System.Text;

namespace WI.CandidateManager.Business.Objects.Helper
{
    /// <summary>
    /// Class CryptoHelper work with hashed passwords.
    /// </summary>
    public sealed class CryptoHelper
    {
        /// <summary>
        /// Salt
        /// </summary>
        private byte[] salt;

        /// <summary>
        /// Hash of password
        /// </summary>
        private byte[] hash;

        #region Constructors
        /// <summary>
        /// Initializes a object based on a hashed password.
        /// </summary>
        /// <param name="hashedPassword">Hashed password with a salt in it.</param>
        public CryptoHelper(string hashedPassword)
        {
            this.salt = Convert.FromBase64String(hashedPassword.Substring(hashedPassword.Length - 8));
            this.hash = Convert.FromBase64String(hashedPassword.Remove(hashedPassword.Length - 8));
        }

        /// <summary>
        /// Initializes a object based on a clear password.
        /// </summary>
        /// <param name="clearText">Clear password.</param>
        public CryptoHelper(char[] clearText)
        {
            salt = GenerateRandom(6);
            hash = HashPassword(clearText);
        }
        #endregion

        /// <summary>
        /// Return the salt.
        /// </summary>
        public string Salt
        {
            get { return Convert.ToBase64String(salt); }
        }

        /// <summary>
        /// Return base64-based hash of password.
        /// </summary>
        public string Hash
        {
            get { return Convert.ToBase64String(hash); }
        }

        /// <summary>
        /// Return hashed password with salt in it.
        /// </summary>
        public string HashedPassword
        {
            get { return Convert.ToBase64String(hash) + Convert.ToBase64String(salt); }
        }

        /// <summary>
        /// Compare clear password with hashed password.
        /// </summary>
        /// <param name="clearText">Clear password.</param>
        /// <returns>Return true if passwords are identical.</returns>
        public bool Verify(string clearText)
        {
            byte[] hash = HashPassword(clearText.ToCharArray());

            if (hash.Length == this.hash.Length)
            {
                for (int i = 0; i < hash.Length; i++)
                {
                    if (hash[i] != this.hash[i])
                    {
                        return false;
                    }
                }

                return true;
            }

            return false;
        }

        /// <summary>
        /// Generate random password.
        /// </summary>
        /// <returns>Return char array-based password.</returns>
        private char[] Generate()
        {
            char[] random = new char[12];

            // генерируем 9 случайных байтов; этого достаточно, чтобы
            // получить 12 случайных символов из набора base64
            byte[] rnd = GenerateRandom(9);

            // конвертируем случайные байты в base64
            Convert.ToBase64CharArray(rnd, 0, rnd.Length, random, 0);

            // очищаем рабочий массив
            Array.Clear(rnd, 0, rnd.Length);

            return random;
        }

        /// <summary>
        /// Generate random byte array.
        /// </summary>
        /// <param name="size">Required length of array.</param>
        /// <returns>Random byte array.</returns>
        private byte[] GenerateRandom(int size)
        {
            byte[] random = new byte[size];
            RandomNumberGenerator.Create().GetBytes(random);
            return random;
        }

        /// <summary>
        /// Hash clear password using salt.
        /// </summary>
        /// <param name="clearText">Clear password.</param>
        /// <returns>Byte array-based hash of password.</returns>
        private byte[] HashPassword(char[] clearText)
        {
            Encoding utf8 = Encoding.UTF8;
            byte[] hash;

            // создаем рабочий массив достаточного размера, чтобы вместить
            byte[] data = new byte[salt.Length
                                   + utf8.GetMaxByteCount(clearText.Length)];

            try
            {
                // копируем синхропосылку в рабочий массив
                Array.Copy(salt, 0, data, 0, salt.Length);

                // копируем пароль в рабочий массив, преобразуя его в UTF-8
                int byteCount = utf8.GetBytes(clearText, 0, clearText.Length,
                    data, salt.Length);

                // хэшируем данные массива
                using (HashAlgorithm alg = new SHA256Managed())
                    hash = alg.ComputeHash(data, 0, salt.Length + byteCount);
            }
            finally
            {
                // очищаем рабочий массив в конце работы, чтобы избежать
                // утечки открытого пароля
                Array.Clear(data, 0, data.Length);
            }

            return hash;
        }

    }
}
